
package org.buildforce.uploader;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.deployer.ArtifactDeployer;
import org.apache.maven.artifact.deployer.ArtifactDeploymentException;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadata;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.ArtifactRepositoryFactory;
import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
import org.apache.maven.model.FileSet;
import org.apache.maven.model.Model;
import org.apache.maven.model.PatternSet;
import org.apache.maven.model.Resource;
import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.artifact.ProjectArtifactMetadata;
import org.buildforce.uploader.metadata.ArtifactMetaData;
import org.buildforce.uploader.metadata.ArtifactMetaDataBuilder;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.WriterFactory;

import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * Uploads a local artifact repository to a remote location.
 * <p/>
 * Please note: Part of this code is a rip off from the maven-deploy-plugin,
 * therefore credits go to the original authors as well.
 *
 * @author <a href="mailto:carlspring@gmail.com">Martin Todorov</a>
 * @author <a href="mailto:aramirez@apache.org">Allan Ramirez</a>
 * @goal upload
 * @requiresProject false
 */
public class UploaderMojo extends AbstractMojo
{

    private static final String[] INCLUDES = { "**/**" };
    private static final String INCLUDES_JARS = "**/*.jar";
    private static final String INCLUDES_WARS = "**/*.war";
    private static final String SNAPSHOT_JARS = "**/**SNAPSHOT**/**.jar";
    private static final String SNAPSHOT_WARS = "**/**SNAPSHOT**/**.war";

    /**
     * The maven project.
     *
     * @parameter expression="${project}"
     * @required
     * @readonly
     */
    private MavenProject project;

    /**
     * @parameter expression="${artifactRepositoryDirectory}" required="true"
     */
    private String artifactRepositoryDirectory;

    /**
     * @parameter expression="${component.org.apache.maven.artifact.deployer.ArtifactDeployer}"
     * @required
     * @readonly
     */
    private ArtifactDeployer deployer;

    /**
     * @parameter expression="${localRepository}" default="${maven.repo.local}"
     */
    private ArtifactRepository localRepository;

    /**
     * Used only if the mode is "copy".
     *
     * @parameter expression="${localDeploymentRepository}" required="false"
     */
    private String localDeploymentRepository;

    /**
     * Server Id to map on the &lt;id&gt; under &lt;server&gt; section of settings.xml
     * In most cases, this parameter will be required for authentication.
     *
     * @parameter expression="${repositoryId}" default-value="remote-repository"
     * @required
     */
    private String repositoryId;

    /**
     * The type of remote repository layout to deploy to. Try <i>legacy</i> for
     * a Maven 1.x-style repository layout.
     *
     * @parameter expression="${repositoryLayout}" default-value="default"
     * @required
     */
    private String repositoryLayout;

    /**
     * Map that contains the layouts
     *
     * @component role="org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout"
     */
    private Map repositoryLayouts;

    /**
     * Component used to create an artifact
     *
     * @component
     */
    private ArtifactFactory artifactFactory;

    /**
     * Component used to create a repository
     *
     * @component
     */
    private ArtifactRepositoryFactory repositoryFactory;

    /**
     * URL where the artifact will be deployed. <br/>
     * ie ( file:///m2-repo or scp://host.com/path/to/repo )
     *
     * @parameter expression="${url}"
     * @required
     */
    private String url;

    /**
     * release / snapshot
     *
     * @parameter expression="${policy}" required="true" default="release"
     */
    private String policy;

    /**
     * deploy / copy
     *
     * @parameter expression="${mode}" required="true" default="deploy"
     */
    private String mode;

    /**
     * @parameter expressions="${includes}"
     */
    private PatternSet includes;

    /**
     * @parameter expressions="${excludes}"
     */
    private PatternSet excludes;


    @Override
    public void execute() throws MojoExecutionException, MojoFailureException
    {
        System.out.println(new File(".").getAbsolutePath());
        System.out.println("artifactRepositoryDirectory = " + artifactRepositoryDirectory);

        String[] artifacts = findArtifacts(artifactRepositoryDirectory);

        // TODO: Convert to debugs:
        getLog().info("   [ Artifacts list start ] ");
        for (final String filePath : artifacts)
        {
            getLog().info("     " +filePath);
        }
        getLog().info("   [ *Artifacts list end* ] ");

        int i = 1;
        for (final String filePath : artifacts)
        {

            getLog().debug(filePath);
            getLog().info("   [ Deploying artifact ] ");
            getLog().info("     --> " + filePath + " (" +i + "/" +artifacts.length +")");

            if (mode.equals("deploy"))
            {
                ArtifactMetaDataBuilder artifactMetaDataBuilder = new ArtifactMetaDataBuilder();
                ArtifactMetaData artifactMetaData = artifactMetaDataBuilder.buildMetaData(filePath);

                deployArtifact(artifactMetaData);
            }
            else
            {
                copyArtifacts(filePath);
            }

            getLog().info("   [ Artifact deployed! ] ");

            i++;
        }
    }


    /**
     * Copies given resources to the build directory.
     *
     * @param   file
     * @throws  MojoExecutionException
     */
    public void copyArtifacts(String file)
            throws MojoExecutionException
    {
        // String[] includedFiles = findArtifacts(artifactRepositoryDirectory);
        // for (Object includedFile : includedFiles)
        {
            // String destination = (String) includedFile;
            String destination = file;

            File source = new File(artifactRepositoryDirectory, destination);
            File destinationFile = new File(localDeploymentRepository, destination);

            if (!destinationFile.getParentFile().exists())
                destinationFile.getParentFile().mkdirs();

            try
            {
                FileUtils.copyFile(source, destinationFile);
            }
            catch (IOException e)
            {
                throw new MojoExecutionException("Error copying web resource " + source, e);
            }
        }
    }

    public void deployArtifact(ArtifactMetaData artifactMetaData)
            throws MojoExecutionException, MojoFailureException
    {
        ArtifactRepositoryLayout layout = (ArtifactRepositoryLayout) repositoryLayouts.get(repositoryLayout);
        ArtifactRepository deploymentRepository = repositoryFactory.createDeploymentArtifactRepository(repositoryId,
                                                                                                       url,
                                                                                                       layout,
                                                                                                       false);

        final File pomFile = generatePomFile(artifactMetaData);

        try
        {
            // Create the artifact
            Artifact artifact;

            if (artifactMetaData.getClassifier().equals(""))
            {
                artifact = artifactFactory.createArtifact(artifactMetaData.getGroupId(),
                                                          artifactMetaData.getArtifactId(),
                                                          artifactMetaData.getVersion(),
                                                          "compile",
                                                          artifactMetaData.getPackaging());
            }
            else
            {
                artifact = artifactFactory.createArtifactWithClassifier(artifactMetaData.getGroupId(),
                                                                        artifactMetaData.getArtifactId(),
                                                                        artifactMetaData.getVersion(),
                                                                        artifactMetaData.getPackaging(),
                                                                        artifactMetaData.getClassifier());
            }

            artifact.setFile(new File(artifactRepositoryDirectory + File.separatorChar +artifactMetaData.getArtifactPathInRepository()));

            // Deploy the POM
            boolean isPomArtifact = "pom".equals(artifactMetaData.getPackaging());
            if (!isPomArtifact)
            {
                ArtifactMetadata metadata = new ProjectArtifactMetadata(artifact, pomFile);
                artifact.addMetadata(metadata);
            }

            try
            {
                if (isPomArtifact)
                {
                    getDeployer().deploy(pomFile, artifact, deploymentRepository, getLocalRepository());
                }
                else
                {
                    final File file = artifact.getFile();

                    if (file != null && !file.isDirectory())
                    {
                        getLog().info("   [ Deploying... ]");
                        getLog().info("     --> " +artifactMetaData.toString() +" <--");

                        if (!Boolean.parseBoolean(System.getProperty("skip.deployment")))
                            getDeployer().deploy(file, artifact, deploymentRepository, getLocalRepository());

                        getLog().info("   [  *Deployed!* ]");
                    }
                    else
                    {
                        String message = "The packaging for this project did not assign a file to the build artifact";
                        throw new MojoExecutionException(message);
                    }
                }
            }
            catch (ArtifactDeploymentException e)
            {
                throw new MojoExecutionException(e.getMessage(), e);
            }
        }
        finally
        {
            //noinspection ResultOfMethodCallIgnored
            pomFile.delete();
        }
    }

    private String[] findArtifacts(String path) throws MojoExecutionException
    {
        DirectoryScanner scanner = new DirectoryScanner();

        FileSet fileSet = new FileSet();
        fileSet.setDirectory(path);

        if (policy.toLowerCase().equals("snapshot"))
        {
            prepareInclusionSet(fileSet);
            // fileSet.addInclude(SNAPSHOT_JARS);
            // fileSet.addInclude(SNAPSHOT_WARS);
            prepareExclusionSet(fileSet);
        }
        else
        {
            prepareInclusionSet(fileSet);
            prepareExclusionSet(fileSet);

            fileSet.addExclude(SNAPSHOT_JARS);
            fileSet.addExclude(SNAPSHOT_WARS);
        }

        // Include default artifact types:
        // fileSet.addInclude(INCLUDES_JARS);
        // fileSet.addInclude(INCLUDES_WARS);

        File resourceDirectory = new File(fileSet.getDirectory());
        String[] finalIncludes = new String[fileSet.getIncludes().size()];
        String[] finalExcludes = new String[fileSet.getExcludes().size()];

        finalIncludes = fileSet.getIncludes().toArray(finalIncludes);
        finalExcludes = fileSet.getExcludes().toArray(finalExcludes);

        System.out.println("Includes: " + Arrays.toString(finalIncludes));
        System.out.println("Excludes: " + Arrays.toString(finalExcludes));

        scanner.setBasedir(resourceDirectory);
        scanner.setIncludes(finalIncludes);

        if (fileSet.getExcludes() != null && !fileSet.getExcludes().isEmpty())
            scanner.setExcludes(finalExcludes);

        scanner.addDefaultExcludes();
        scanner.scan();

        return scanner.getIncludedFiles();
    }

    private void prepareInclusionSet(FileSet fileSet)
    {
        if (includes != null)
        {
            List<String> includesList = includes.getIncludes();
            for (String inclusion : includesList)
            {
                fileSet.addInclude(inclusion);
            }
        }

        prepareFileSet(fileSet, "artifact.inclusion.patterns", true);
    }

    private void prepareExclusionSet(FileSet fileSet)
    {
        if (excludes != null)
        {
            List<String> excludesList = excludes.getExcludes();
            for (String exclusion : excludesList)
            {
                fileSet.addExclude(exclusion);
            }
        }

        prepareFileSet(fileSet, "artifact.exclusion.patterns", false);
    }
    
    private void prepareFileSet(FileSet fileSet, String property, boolean isInclusion)
    {
        String patternProperty = System.getProperty(property);
        if (patternProperty != null)
        {
            String[] patterns = patternProperty.split(",");
            for (String pattern : patterns)
            {
                if (isInclusion)
                    fileSet.addInclude(pattern.trim());
                else
                    fileSet.addExclude(pattern.trim());
            }
        }
    }

    private File generatePomFile(ArtifactMetaData artifactMetaData)
            throws MojoExecutionException
    {
        Writer fw = null;
        try
        {
            File tempFile = File.createTempFile("mvninstall", ".pom");
            tempFile.deleteOnExit();

            Model model = new Model();
            model.setModelVersion("4.0.0");
            model.setGroupId(artifactMetaData.getGroupId());
            model.setArtifactId(artifactMetaData.getArtifactId());
            model.setVersion(artifactMetaData.getVersion());
            model.setPackaging(artifactMetaData.getPackaging());
            // model.setDescription(description);

            fw = WriterFactory.newXmlWriter(tempFile);
            new MavenXpp3Writer().write(fw, model);

            return tempFile;
        }
        catch (IOException e)
        {
            throw new MojoExecutionException("Error writing temporary pom file: " + e.getMessage(), e);
        }
        finally
        {
            IOUtil.close(fw);
        }
    }

    public ArtifactDeployer getDeployer()
    {
        return deployer;
    }

    public void setDeployer(ArtifactDeployer deployer)
    {
        this.deployer = deployer;
    }

    public ArtifactRepository getLocalRepository()
    {
        return localRepository;
    }

    public void setLocalRepository(ArtifactRepository localRepository)
    {
        this.localRepository = localRepository;
    }

}
